{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "0",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "# Writing a software application using function calls\n",
    "\n",
    "The default way of creating code in Autogen is its built-in code extractor. Although it allows for creating and executing simple scripts fast, that way of creating code is not suitable for developing advanced software applications, according to my experiences. The process of developing an application is mostly the process of introducing changes into existing files rather than creating new files with code. And in my experience, the code extractor is bad at introducing changes as the model often gets lost and can damage existing files.\n",
    "\n",
    "Properly created functions that can modify code provide us with the ability to have more control over code changes and result in better quality. Additionally, as the scope of possible operations is predefined inside the tools, we can safely use Autogen without Docker, avoiding all the complications related to it.\n",
    "\n",
    "## Requirements"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1",
   "metadata": {},
   "outputs": [],
   "source": [
    "! pip install ag2"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "2",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "## Set your API Endpoint"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "import autogen\n",
    "\n",
    "llm_config = autogen.LLMConfig(\n",
    "    config_list={\n",
    "        \"model\": \"gpt-4-turbo-preview\",\n",
    "        \"api_key\": os.getenv(\"OPENAI_API_KEY\"),\n",
    "        \"temperature\": 0,\n",
    "    }\n",
    ")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "4",
   "metadata": {},
   "source": [
    "## Create agents\n",
    "\n",
    "In this example, we will improve a simple FastAPI application using only dedicated function calls. Let's create an Engineer agent that will think out and execute code changes, and a user proxy Admin agent, through which we will guide our Engineer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5",
   "metadata": {},
   "outputs": [],
   "source": [
    "engineer = autogen.AssistantAgent(\n",
    "    name=\"Engineer\",\n",
    "    system_message=\"\"\"\n",
    "    I'm Engineer. I'm expert in python programming. I'm executing code tasks required by Admin.\n",
    "    \"\"\",\n",
    "    llm_config=llm_config,\n",
    ")\n",
    "\n",
    "user_proxy = autogen.UserProxyAgent(\n",
    "    name=\"Admin\",\n",
    "    human_input_mode=\"ALWAYS\",\n",
    "    code_execution_config=False,\n",
    ")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "6",
   "metadata": {},
   "source": [
    "Mention, unlike in many other examples, here we don't need a separate Executor agent to save our code, as that will be done by functions. We also don't need Docker to be running because of that - which makes the entire process easier.\n",
    "\n",
    "Next, let's set up our group chat."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7",
   "metadata": {},
   "outputs": [],
   "source": [
    "groupchat = autogen.GroupChat(\n",
    "    agents=[engineer, user_proxy],\n",
    "    messages=[],\n",
    "    max_round=500,\n",
    "    speaker_selection_method=\"round_robin\",\n",
    "    enable_clear_history=True,\n",
    ")\n",
    "\n",
    "manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=llm_config)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "8",
   "metadata": {},
   "source": [
    "## Prepare appropriate functions\n",
    "\n",
    "Let's go to core of the thing. Prepare functions that provide Engineer with functionality to modify existing code, create new code files, check filesystem and files.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated\n",
    "\n",
    "default_path = \"backend_dir/\"\n",
    "\n",
    "\n",
    "@user_proxy.register_for_execution()\n",
    "@engineer.register_for_llm(description=\"List files in chosen directory.\")\n",
    "def list_dir(directory: Annotated[str, \"Directory to check.\"]):\n",
    "    files = os.listdir(default_path + directory)\n",
    "    return 0, files\n",
    "\n",
    "\n",
    "@user_proxy.register_for_execution()\n",
    "@engineer.register_for_llm(description=\"Check the contents of a chosen file.\")\n",
    "def see_file(filename: Annotated[str, \"Name and path of file to check.\"]):\n",
    "    with open(default_path + filename) as file:\n",
    "        lines = file.readlines()\n",
    "    formatted_lines = [f\"{i + 1}:{line}\" for i, line in enumerate(lines)]\n",
    "    file_contents = \"\".join(formatted_lines)\n",
    "\n",
    "    return 0, file_contents\n",
    "\n",
    "\n",
    "@user_proxy.register_for_execution()\n",
    "@engineer.register_for_llm(description=\"Replace old piece of code with new one. Proper indentation is important.\")\n",
    "def modify_code(\n",
    "    filename: Annotated[str, \"Name and path of file to change.\"],\n",
    "    start_line: Annotated[int, \"Start line number to replace with new code.\"],\n",
    "    end_line: Annotated[int, \"End line number to replace with new code.\"],\n",
    "    new_code: Annotated[str, \"New piece of code to replace old code with. Remember about providing indents.\"],\n",
    "):\n",
    "    with open(default_path + filename, \"r+\") as file:\n",
    "        file_contents = file.readlines()\n",
    "        file_contents[start_line - 1 : end_line] = [new_code + \"\\n\"]\n",
    "        file.seek(0)\n",
    "        file.truncate()\n",
    "        file.write(\"\".join(file_contents))\n",
    "    return 0, \"Code modified\"\n",
    "\n",
    "\n",
    "@user_proxy.register_for_execution()\n",
    "@engineer.register_for_llm(description=\"Create a new file with code.\")\n",
    "def create_file_with_code(\n",
    "    filename: Annotated[str, \"Name and path of file to create.\"], code: Annotated[str, \"Code to write in the file.\"]\n",
    "):\n",
    "    with open(default_path + filename, \"w\") as file:\n",
    "        file.write(code)\n",
    "    return 0, \"File created successfully\""
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "10",
   "metadata": {},
   "source": [
    "## Prepare code to work with\n",
    "\n",
    "In this example, we will show how AI can extend the functionalities of existing code, as improving existing code is a much more frequent use case in software development than creating a new one. Let's prepare the initial code on which we will work. That will be a simple FastAPI script that will allow you to calculate today's stock spread in percentage for CD Project Red, a famous Polish gamedev company. Create a folder called 'backend_dir' and place a 'main.py' file here with the following content:"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "11",
   "metadata": {},
   "source": [
    "```python\n",
    "# backend_dir/main.py\n",
    "\n",
    "from fastapi import FastAPI\n",
    "import yfinance as yf\n",
    "\n",
    "app = FastAPI()\n",
    "\n",
    "@app.get(\"/cdr_daily_spread\")\n",
    "async def calculate_daily_spread():\n",
    "    cdr = yf.Ticker(\"CDR.WA\")\n",
    "    today_data = cdr.history(period=\"1d\")\n",
    "    spread = ((today_data[\"High\"] - today_data[\"Low\"]) / today_data[\"Low\"]) * 100\n",
    "    return spread\n",
    "```"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "12",
   "metadata": {},
   "source": [
    "Install needed libraries. We can run our API using:\n",
    "\n",
    "```bash\n",
    "uvicorn main:app --reload\n",
    "```\n",
    "\n",
    "Send a request to 'localhost:8000/cdr_daily_spread' to check if it works.\n",
    "\n",
    "## Edit code using agents\n",
    "\n",
    "Let's assume we want our agents to extend the functionality of the application. Let's modify the endpoint to check the spread also for 11bits, another gamedev company, and compare it for both stocks. Also, let's separate the internal logic into a different file.\n",
    "\n",
    "Finally, instantiate a chat between the Engineer and the Admin. It will start by exploring the filesystem first, and after that, it will wait for our orders. Then, we will explain the task to the Engineer and ask him to provide a plan of changes first - according to my experience, that greatly increases the quality of LLM responses.\n",
    "\n",
    "After that, introduce changes with the Engineer one after another. Ask him to correct something or improve the functionality if needed. Do not hesitate to interrupt the tool's execution if you feel he is going to do something wrong. If errors occur, provide him with the error log and ask him to check out the file to refresh his knowledge about it before actually introducing changes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "13",
   "metadata": {},
   "outputs": [],
   "source": [
    "chat_result = user_proxy.initiate_chat(\n",
    "    manager,\n",
    "    message=\"\"\"\n",
    "You will need to improve app in FastApi. For now, check out all the application files, try to understand it and wait for next instructions.\n",
    "\"\"\",\n",
    ")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "14",
   "metadata": {},
   "source": [
    "## Result\n",
    "\n",
    "Finally, our agents modified a code so it looks like this:"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "15",
   "metadata": {},
   "source": [
    "```python\n",
    "# backend_dir/main.py\n",
    "\n",
    "from fastapi import FastAPI\n",
    "from spread_calculation import calculate_daily_spread\n",
    "import yfinance as yf\n",
    "\n",
    "app = FastAPI()\n",
    "\n",
    "@app.get(\"/compare_daily_spread\")\n",
    "async def compare_daily_spread():\n",
    "    cdr_spread = calculate_daily_spread(\"CDR.WA\")\n",
    "    bits_spread = calculate_daily_spread(\"11B.WA\")\n",
    "    spread_difference = cdr_spread - bits_spread\n",
    "    if spread_difference > 0:\n",
    "        return {'message': 'CD Project Red has a larger daily spread', 'difference': spread_difference}\n",
    "    elif spread_difference < 0:\n",
    "        return {'message': '11bits Studio has a larger daily spread', 'difference': -spread_difference}\n",
    "    else:\n",
    "        return {'message': 'Both stocks have the same daily spread', 'difference': 0}\n",
    "\n",
    "\n",
    "# backend_dir/spread_calculation.py\n",
    "\n",
    "import yfinance as yf\n",
    "\n",
    "def calculate_daily_spread(ticker):\n",
    "    stock = yf.Ticker(ticker)\n",
    "    today_data = stock.history(period=\"1d\")\n",
    "    spread = ((today_data[\"High\"] - today_data[\"Low\"]) / today_data[\"Low\"]) * 100\n",
    "    return spread.values[0]\n",
    "```"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "16",
   "metadata": {},
   "source": [
    "You can check out work of application with Postman or curl and see the next output:"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "17",
   "metadata": {},
   "source": [
    "```json\n",
    "{\n",
    "    \"message\": \"11bits Studio has a larger daily spread\",\n",
    "    \"difference\": 1.7968083865943187\n",
    "}\n",
    "```"
   ]
  }
 ],
 "metadata": {
  "front_matter": {
   "description": "Equip your agent with functions that can efficiently implement features into your software application.",
   "tags": [
    "code generation",
    "tool/function",
    "fastapi",
    "software engineering"
   ]
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
